﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using log4net;

namespace TruyenTranhCrawler.Utils
{
    public class HtmlParser
    {
        private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        #region Constants
        public class Patterns
        {
            public const string CommentPattern = @"";
            public const string MultiSpacePattern = @"(\s|$)+";
            public const string SINGLE_SPACE = " ";
            public const string NEW_LINE = "\r\n";
            public const string BREAK = @"(\r\n|\r|\n|\t)";
            public const string TAB = "\t";
        }
        #endregion

        private string _fHtml;
        public HtmlParser()
        {
            _fHtml = String.Empty;
        }

        #region Convert methods
        public string Replace(string html, string pattern, string replacer)
        {
            _fHtml = html;

            Replace(pattern, replacer);

            return _fHtml.Trim();
        }
        public string ReplaceProperty(string html, string[] propertyName, string[] replacer)
        {
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            for (int i = 0; i < propertyName.Length; i++)
            {
                Replace(GetProperty(propertyName[i]), replacer.Length > i ? replacer[i] : "");
            }
            return _fHtml.Trim();
        }

        public string RemoveRedundantTag(string html, string[] sTag)
        {
            return ReplacePropertiesInTag(html, sTag, new string[] { });
        }
        /// <summary>
        /// Replace all of properties in tags by other string.
        /// </summary>
        public string ReplacePropertiesInTag(string html, string[] sTag, string[] sNewProperties)
        {
            //replace   with white space
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            //remove whitespaces and line breaks
            Replace(Patterns.MultiSpacePattern, Patterns.SINGLE_SPACE);

            for (int i = 0; i < sTag.Length; i++)
            {
                try
                {
                    Replace(GetOpeningTagPattern(sTag[i]), "<" + sTag[i] + " " + sNewProperties[i] + ">");
                }
                catch
                {
                    Replace(GetOpeningTagPattern(sTag[i]), "<" + sTag[i] + ">");
                }
            }
            return _fHtml.Trim();
        }

        /// <summary>
        /// Replace tags by other tags.
        /// </summary>
        public string ReplaceTag(string html, string[] sTag, string[] sReplacer)
        {
            if (sTag.Length > sReplacer.Length)
                return html;

            //replace   with white space
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            //remove whitespaces and line breaks
            Replace(Patterns.MultiSpacePattern, Patterns.SINGLE_SPACE);

            for (int i = 0; i < sTag.Length; i++)
            {
                string repOpenTag = (sReplacer[i] == "") ? "" : "<" + sReplacer[i] + ">";
                string repClosingTag = (sReplacer[i] == "") ? "" : "</" + sReplacer[i] + ">";
                repClosingTag += Patterns.NEW_LINE;

                Replace(GetOpeningTagPattern(sTag[i]), repOpenTag);
                Replace(GetClosingTagPattern(sTag[i]), repClosingTag);
            }

            //Vi ham Regex nhieu khi khong loc duoc het
            //Loc lai lan nua
            string sContent = _fHtml;

            for (int i = 0; i < sTag.Length; i++)
            {
                int iBeginOpenTag = 0;
                int iEndOpenTag = 0;

                string OpenTag = "<" + sTag[i].ToLower();
                string EndOpenTag = ">";
                string CloseTag = "</" + sTag[i].ToLower() + ">";
                string repOpenTag = (sReplacer[i] == "") ? "" : "<" + sReplacer[i].ToLower() + ">";
                string repClosingTag = (sReplacer[i] == "") ? "" : "</" + sReplacer[i].ToLower() + ">";
                string repSingleTag = (sReplacer[i] == "") ? "" : "<" + sReplacer[i].ToLower() + "/>";
                repClosingTag += Patterns.NEW_LINE;

                string substr = "";

                //Replace Open tag and Single tag
                iBeginOpenTag = sContent.ToLower().IndexOf(OpenTag);
                while (iBeginOpenTag != -1)
                {
                    iEndOpenTag = sContent.ToLower().IndexOf(EndOpenTag, iBeginOpenTag);
                    if (iEndOpenTag != -1)
                    {
                        iEndOpenTag += EndOpenTag.Length;
                        substr = sContent.Substring(iBeginOpenTag, iEndOpenTag - iBeginOpenTag);

                        int iFrom = 1;
                        if (substr[substr.Length - 2] == '/' && substr.ToLower() != repClosingTag)
                        {
                            sContent = sContent.Replace(substr, repSingleTag);
                            iFrom = iBeginOpenTag + repSingleTag.Length;
                            iBeginOpenTag = sContent.ToLower().IndexOf(OpenTag, iFrom - 1);
                        }
                        else if (substr.ToLower() != repOpenTag)
                        {
                            sContent = sContent.Replace(substr, repOpenTag);
                            iFrom = iBeginOpenTag + repOpenTag.Length;
                            iBeginOpenTag = sContent.ToLower().IndexOf(OpenTag, iFrom - 1);
                        }
                        else
                        {
                            iBeginOpenTag = -1;
                        }
                    }
                    else
                        break;
                }

                //Replce Close tag
                sContent = sContent.Replace(CloseTag, repClosingTag);
            }
            return sContent.Trim();
        }

        /// <summary>
        /// Convert to Text only.
        /// </summary>
        public string RemoveAllTag(string html)
        {
            const string pattern = @"(<(.*?)>|<(.*?)/>)";
            return Replace(html, pattern, string.Empty);
        }

        /// <summary>
        /// Remove only tag, keep content in it.
        /// </summary>
        public string RemoveTagOnly(string html, string[] sTag)
        {
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            //Remove all remaining html tags
            foreach (string tag in sTag)
            {
                if (tag != null && tag != "")
                {
                    Remove(GetOpeningTagPattern(tag));
                    Remove(GetClosingTagPattern(tag));
                    Remove(GetSingleTagPattern(tag));
                }
            }
            //Vi ham Regex nhieu khi khong loc duoc het
            //Loc lai lan nua
            string sContent = _fHtml;

            int iBeginOpenTag = 0;
            int iEndOpenTag = 0;
            string substr = "";
            string replacer = "\r\n";
            foreach (string tag in sTag)
            {
                string OpenTag = "<" + tag.ToLower();
                string EndOpenTag = ">";
                string CloseTag = "</" + tag.ToLower() + ">";

                //Remove Open tag and Single tag
                iBeginOpenTag = sContent.ToLower().IndexOf(OpenTag);
                while (iBeginOpenTag != -1)
                {
                    iEndOpenTag = sContent.ToLower().IndexOf(EndOpenTag, iBeginOpenTag);
                    if (iEndOpenTag != -1)
                    {
                        iEndOpenTag += EndOpenTag.Length;
                        substr = sContent.Substring(iBeginOpenTag, iEndOpenTag - iBeginOpenTag);
                        sContent = sContent.Replace(substr, replacer);

                        iBeginOpenTag = sContent.ToLower().IndexOf(OpenTag);
                    }
                    else
                        iBeginOpenTag = -1;
                }

                //Remove Close tag
                sContent = sContent.Replace(CloseTag, replacer);
            }
            return sContent.Trim();
        }

        /// <summary>
        /// Remove all tag and content in closeable tags. ex: [td]...[/td]
        /// </summary>
        public string RemoveCloseableTags(string html, string[] sTag)
        {
            //replace   with white space
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            //remove whitespaces and line breaks
            Replace(Patterns.MultiSpacePattern, Patterns.SINGLE_SPACE);

            foreach (string tag in sTag)
            {
                Remove(GetOpeningTagPattern(tag));
                Replace(GetClosingTagPattern(tag), Patterns.NEW_LINE);
            }
            return _fHtml.Trim();
        }

        /// <summary>
        /// Remove single tags. ex: [img .../]
        /// </summary>
        public string RemoveSingleTags(string html, string[] sTag)
        {
            //replace   with white space
            _fHtml = html.Replace(" ", Patterns.SINGLE_SPACE);

            //remove whitespaces and line breaks
            Replace(Patterns.MultiSpacePattern, Patterns.SINGLE_SPACE);

            foreach (string tag in sTag)
            {
                Replace(GetSingleTagPattern(tag), Patterns.NEW_LINE);
            }
            return _fHtml.Trim();
        }

        /// <summary>
        /// Remove all in tags.
        /// </summary>
        public string RemoveTagAndContentInTags(string sContent, string[] sTag)
        {
            if (sTag.Length <= 0 || sTag[0] == "") return sContent;

            try
            {
                string sTemp = sContent.Replace(" ", Patterns.SINGLE_SPACE);
                string sTempLower = sTemp.ToLower();
                foreach (string tag in sTag)
                {
                    var tagLower = tag.ToLower();
                    int iFirstIndex = 0;
                    //Find begin tag
                    iFirstIndex = sTempLower.LastIndexOf("<" + tagLower);   //find latest open tag
                    while (iFirstIndex >= 0)
                    {
                        int iLastIndex = sTempLower.IndexOf("</" + tagLower + ">", iFirstIndex);    //find next closed tag
                        if (iLastIndex == -1) break;

                        //Process with sTag
                        sTemp = sTemp.Remove(iFirstIndex, iLastIndex - iFirstIndex + tagLower.Length + 3);    //remove between them
                        sTempLower = sTemp.ToLower();
                        iFirstIndex = sTempLower.LastIndexOf("<" + tagLower);   //for next turn
                    }
                }
                return sTemp;
            }
            catch
            {
                return sContent;
            }
        }

        /// <summary>
        /// Remove only content in nodes.
        /// </summary>
        public string RemoveContentInNode(string html, string[] sBeginNode, string[] sEndNode, string matchedKeys = null)
        {
            string sContent = html;
            for (int i = 0; i < sBeginNode.Length; i++)
            {
                string beginNode = sBeginNode[i].ToLower();
                string endNode = sEndNode[i].ToLower();

                var list = GetContentInNode(sContent, beginNode, endNode);
                foreach (string s in list)
                {
                    if (matchedKeys != null)
                    {
                        //chi remove nhung doan matched
                        if (Matched(s, matchedKeys))
                            sContent = Remove(sContent, s);
                    }
                    else
                    {
                        sContent = Remove(sContent, s);
                    }
                }
            }

            return sContent;
        }
        private string Remove(string html, string sRemove)
        {
            var iFrom = html.IndexOf(sRemove, StringComparison.OrdinalIgnoreCase);
            if (iFrom >= 0)
            {
                return html.Remove(iFrom, sRemove.Length);
            }
            return html;
        }

        private readonly string[] _fixedTemplates = new[] { "(", ")" };
        /// <summary>
        /// Get content between node (include nodes).
        /// </summary>
        public List<string> GetContentInNode(string html, string sBeginNode, string sEndNode, string matchPattern = null)
        {
            string sContent = html;
            var getOuts = new List<string>();
            try
            {
                var openTagPattern = sBeginNode;
                var closeTagPattern = sEndNode;
                if (openTagPattern.IndexOf("<") >= 0)
                {
                    var split = sBeginNode.Split(' ');
                    openTagPattern = split[0]; //vi du sBeginNode = '<div class="test"' thi opentag = '<div'
                    //nguyen tắc:
                    //tìm thẻ đóng tương ứng với thẻ mở
                }
                //fix some special charecters
                foreach (string sfix in _fixedTemplates)
                {
                    var idx = openTagPattern.IndexOf(sfix);
                    if (idx >= 0)
                    {
                        openTagPattern = openTagPattern.Insert(idx, @"\");
                    }
                    idx = closeTagPattern.IndexOf(sfix);
                    if (idx >= 0)
                    {
                        closeTagPattern = closeTagPattern.Insert(idx, @"\");
                    }
                }
                //tìm cụm nội dung đầu tiên
                int iFirstIndex = sContent.LastIndexOf(sBeginNode, StringComparison.OrdinalIgnoreCase);
                while (iFirstIndex >= 0)
                {
                    int iLastIndex = sContent.IndexOf(sEndNode, iFirstIndex + openTagPattern.Length, StringComparison.OrdinalIgnoreCase);
                    if (iLastIndex >= 0)
                    {
                        while (true)
                        {
                            iLastIndex += sEndNode.Length;
                            string substr = sContent.Substring(iFirstIndex, iLastIndex - iFirstIndex);
                            var countOpen = Regex.Matches(substr, openTagPattern).Count;
                            var countClose = Regex.Matches(substr, closeTagPattern).Count;
                            if (countOpen == countClose)
                            {
                                //đúng là endNode của beginNode cần tìm
                                //check dieu kien
                                if (!string.IsNullOrEmpty(matchPattern))
                                {
                                    if (Matched(substr, matchPattern))
                                        getOuts.Insert(0, substr);
                                }
                                else
                                    getOuts.Insert(0, substr);
                                sContent = sContent.Remove(iFirstIndex, substr.Length); //xoa bo cum nay trong sources
                                break;  //thoat vong lap
                            }
                            iLastIndex = sContent.IndexOf(sEndNode, iLastIndex, StringComparison.OrdinalIgnoreCase);
                            if (iLastIndex < 0)
                            {
                                //neu da ko tim thay the dong tuong thich nua, thi lay doan substring o tren
                                if (!string.IsNullOrEmpty(matchPattern))
                                {
                                    if (Matched(substr, matchPattern))
                                        getOuts.Insert(0, substr);
                                }
                                else
                                    getOuts.Insert(0, substr);
                                sContent = sContent.Remove(iFirstIndex, substr.Length); //xoa bo cum nay trong sources
                                break;  //thoat vong lap
                            }
                        }

                        //tìm cụm khác thỏa mãn
                        iFirstIndex = sContent.LastIndexOf(sBeginNode, StringComparison.OrdinalIgnoreCase);
                    }
                    else
                        break;
                }
            }
            catch (Exception ex)
            {
                Logger.Info("*** ERROR " + this + " GetContentInNode " + ex);
            }
            return getOuts;
        }

        public string GetFirstImg(string sContent)
        {
            var parser = new HtmlParser();
            var imgs = parser.GetContentInNode(sContent, "<img ", ">");
            if (imgs.Count > 0)
            {
                var srcs = parser.GetProperty(imgs[0], "src");
                if (srcs.Count > 0)
                {
                    return srcs[0].Groups[4].Value;
                }
            }
            return "";
        }
        /// <summary>
        /// Get matching from tag. call Groups[3].Value to get Content in tag
        /// </summary>
        /// <param name="sSource"></param>
        /// <param name="sTag"></param>
        /// <returns></returns>
        public MatchCollection GetBetweenTag(string sSource, string sTag)
        {
            Regex r = new Regex(GetBetweenTagsPattern(sTag), RegexOptions.IgnoreCase);

            return r.Matches(sSource);
        }
        public MatchCollection GetBetweenSingleTags(string sSource, string sTag)
        {
            Regex r = new Regex(GetSingleTagPattern(sTag), RegexOptions.IgnoreCase);

            return r.Matches(sSource);
        }
        public MatchCollection GetProperty(string sSource, string propertyName)
        {
            Regex r = new Regex(GetProperty(propertyName), RegexOptions.IgnoreCase);

            return r.Matches(sSource);
        }

        public string RemoveProperties(string html, string[] sNewProperties)
        {
            return ReplaceProperty(html, sNewProperties, new string[] { });
        }
        public bool Matched(string sources, string pattern)
        {
            return Regex.Matches(sources, pattern, (RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)).Count > 0;
        }
        #endregion

        #region GetTag Methods (Do not change!)

        public string GetProperty(string propertyName)
        {
            return String.Format(@"(\s*){0}(\s*)=(\s*)[\""|\'](.*?)[\""|\']", propertyName);
        }

        public string GetOpeningTagPattern(string tag)
        {
            return String.Format(@"<(\s*){0}(.*?)>", tag);
        }
        public string GetClosingTagPattern(string tag)
        {
            return String.Format(@"</{0}>", tag);
        }

        public string GetBetweenTagsPattern(string tag)
        {
            return String.Format("{0}(.*?){1}", GetOpeningTagPattern(tag), GetClosingTagPattern(tag));
        }

        public string GetSingleTagPattern(string tag)
        {
            return String.Format(@"<(\s*){0}(.*?)>", tag);
        }

        public void Remove(string pattern)
        {
            Replace(pattern, String.Empty);
        }

        private void Replace(string pattern, string replacer)
        {
            _fHtml = Regex.Replace(_fHtml, pattern, replacer, (RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
        }
        public static string ReplaceMultiSpace(string sources)
        {
            return Regex.Replace(sources, Patterns.MultiSpacePattern, Patterns.SINGLE_SPACE, (RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
        }
        #endregion
    }
}
